home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue61 / Alfresco / AACache.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2000-08-08  |  16.4 KB  |  533 lines

  1. {*********************************************************}
  2. {* AACache                                               *}
  3. {* Copyright (c) Julian M Bucknall 2000                  *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Algorithms Alfresco: Browser file cache class         *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. unit AACache;
  14.  
  15. interface
  16.  
  17. uses
  18.   Windows,
  19.   SysUtils,
  20.   Classes,
  21.   AABufStm,
  22.   AAHshLnP,
  23.   AAPriQue;
  24.  
  25. type
  26.   TaaFileCache = class
  27.     private
  28.       FCurDiskSize   : integer; 
  29.       FFolder        : string;
  30.       FItems         : TaaHashTableLinear;
  31.       FExpiryQueue   : TaaPriorityQueueEx;
  32.       FLastUsedQueue : TaaPriorityQueueEx;
  33.       FMaxDiskSize   : integer;
  34.     protected
  35.       procedure fcSetFolder(const aName : string);
  36.  
  37.       function fcAddItem(aCacheItem : pointer) : boolean;
  38.       procedure fcCleanUp;
  39.       function fcGetQualifiedFileName(const aFileName : string) : string;
  40.       function fcGetUniqueFileName : string;
  41.  
  42.       procedure fcLoadFromFile;
  43.       procedure fcLoadFromStream(aStream : TStream);
  44.       procedure fcSaveToFile;
  45.       procedure fcSaveToStream(aStream : TStream);
  46.     public
  47.       constructor Create;
  48.       destructor Destroy; override;
  49.  
  50.       procedure Add(const aExternalName : string;
  51.                     const aExpiryDate   : TDateTime;
  52.                           aStream       : TStream);
  53.  
  54.       procedure Clear;
  55.  
  56.       procedure Delete(const aExternalName : string);
  57.  
  58.       function Get(const aExternalName : string) : TStream;
  59.       procedure GetComplete(const aExternalName : string);
  60.  
  61.       property Folder : string read FFolder write fcSetFolder;
  62.       property MaxDiskSize : integer read FMaxDiskSize write FMaxDiskSize;
  63.   end;
  64.  
  65. implementation
  66.  
  67. const
  68.   CacheFileName = 'AACACHE.IDX';
  69.  
  70. {===TCacheItem=======================================================}
  71. type
  72.   TCacheItem = class
  73.     private
  74.       FDataStream     : TStream; 
  75.       FDownloadDate   : TDateTime;
  76.       FExpiryDate     : TDateTime;
  77.       FExpiryHandle   : TaaPQHandle;
  78.       FExternalName   : string;
  79.       FInternalName   : string;
  80.       FLastUsedDate   : TDateTime;
  81.       FLastUsedHandle : TaaPQHandle;
  82.       FSize           : longint;
  83.     protected
  84.     public
  85.       constructor Create(const aInternalName : string;
  86.                          const aExternalName : string;
  87.                                aExpiryDate   : TDateTime;
  88.                                aDownloadDate : TDateTime;
  89.                                aSize         : longint);
  90.       constructor LoadFromStream(aStream : TStream);
  91.       destructor Destroy; override;
  92.  
  93.       procedure Use;
  94.  
  95.       procedure SaveToStream(aStream : TStream);
  96.  
  97.       property DownloadDate : TDateTime read FDownloadDate;
  98.       property ExpiryDate : TDateTime read FExpiryDate;
  99.       property ExternalName : string read FExternalName;
  100.       property InternalName : string read FInternalName;
  101.       property LastUsedDate : TDateTime read FLastUsedDate;
  102.       property Size : longint read FSize;
  103.  
  104.       property ExpiryHandle : TaaPQHandle
  105.                   read FExpiryHandle write FExpiryHandle;
  106.       property LastUsedHandle : TaaPQHandle
  107.                   read FLastUsedHandle write FLastUsedHandle;
  108.       property DataStream : TStream read FDataStream write FDataStream;
  109.   end;
  110. {--------}
  111. constructor TCacheItem.Create(const aInternalName : string;
  112.                               const aExternalName : string;
  113.                                     aExpiryDate   : TDateTime;
  114.                                     aDownloadDate : TDateTime;
  115.                                     aSize         : longint);
  116. begin
  117.   inherited Create;
  118.   {set internal fields}
  119.   FInternalName := aInternalName;
  120.   FExternalName := aExternalName;
  121.   if (aExpiryDate = 0.0) then
  122.     FExpiryDate := FDownloadDate + 7.0
  123.   else
  124.     FExpiryDate := aExpiryDate;
  125.   FDownloadDate := aDownloadDate;
  126.   FSize := aSize;
  127.   {set the lastused date}
  128.   FLastUsedDate := Now;
  129. end;
  130. {--------}
  131. constructor TCacheItem.LoadFromStream(aStream : TStream);
  132. var
  133.   Len : integer;
  134. begin
  135.   inherited Create;
  136.   {read in the internal name}
  137.   aStream.ReadBuffer(Len, sizeof(Len));
  138.   SetLength(FInternalName, Len);
  139.   aStream.ReadBuffer(FInternalName[1], Len);
  140.   {read in the extternal name}
  141.   aStream.ReadBuffer(Len, sizeof(Len));
  142.   SetLength(FExternalName, Len);
  143.   aStream.ReadBuffer(FExternalName[1], Len);
  144.   {read in the dates}
  145.   aStream.ReadBuffer(FDownloadDate, sizeof(TDateTime));
  146.   aStream.ReadBuffer(FExpiryDate, sizeof(TDateTime));
  147.   aStream.ReadBuffer(FLastUsedDate, sizeof(TDateTime));
  148.   {read in the size of the file}
  149.   aStream.ReadBuffer(FSize, sizeof(FSize));
  150. end;
  151. {--------}
  152. destructor TCacheItem.Destroy;
  153. begin
  154.   inherited Destroy;
  155. end;
  156. {--------}
  157. procedure TCacheItem.SaveToStream(aStream : TStream);
  158. var
  159.   Len : integer;
  160. begin
  161.   {write out the internal name}
  162.   Len := length(FInternalName);
  163.   aStream.WriteBuffer(Len, sizeof(Len));
  164.   aStream.WriteBuffer(FInternalName[1], Len);
  165.   {write out the external name}
  166.   Len := length(FExternalName);
  167.   aStream.WriteBuffer(Len, sizeof(Len));
  168.   aStream.WriteBuffer(FExternalName[1], Len);
  169.   {write out the dates}
  170.   aStream.WriteBuffer(FDownloadDate, sizeof(TDateTime));
  171.   aStream.WriteBuffer(FExpiryDate, sizeof(TDateTime));
  172.   aStream.WriteBuffer(FLastUsedDate, sizeof(TDateTime));
  173.   {write out the size of the file}
  174.   aStream.WriteBuffer(FSize, sizeof(FSize));
  175. end;
  176. {--------}
  177. procedure TCacheItem.Use;
  178. begin
  179.   FLastUsedDate := Now;
  180. end;
  181. {====================================================================}
  182.  
  183.  
  184. {===Helper routines for the cache====================================}
  185. function CompareExpiryDates(const aItem1, aItem2 : pointer) : integer;
  186. var
  187.   CacheItem1 : TCacheItem;
  188.   CacheItem2 : TCacheItem;
  189. begin
  190.   {note: this reverses the usual sense of the comparison so that the
  191.          smallest expiry date (ie the earliest) is retrieved first}
  192.   CacheItem1 := TCacheItem(aItem1);
  193.   CacheItem2 := TCacheItem(aItem2);
  194.   if (CacheItem1.ExpiryDate < CacheItem2.ExpiryDate) then
  195.     Result := 1
  196.   else if (CacheItem1.ExpiryDate = CacheItem2.ExpiryDate) then
  197.     Result := 0
  198.   else
  199.     Result := -1;
  200. end;
  201. {--------}
  202. function CompareLastUsedDates(const aItem1, aItem2 : pointer) : integer;
  203. var
  204.   CacheItem1 : TCacheItem;
  205.   CacheItem2 : TCacheItem;
  206. begin
  207.   {note: this reverses the usual sense of the comparison so that the
  208.          smallest last-used date (ie the earliest) is retrieved first}
  209.   CacheItem1 := TCacheItem(aItem1);
  210.   CacheItem2 := TCacheItem(aItem2);
  211.   if (CacheItem1.LastUsedDate < CacheItem2.LastUsedDate) then
  212.     Result := 1
  213.   else if (CacheItem1.LastUsedDate = CacheItem2.LastUsedDate) then
  214.     Result := 0
  215.   else
  216.     Result := -1;
  217. end;
  218. {====================================================================}
  219.  
  220.  
  221. {===TaaFileCache=====================================================}
  222. constructor TaaFileCache.Create;
  223. begin
  224.   inherited Create;
  225.   {create the containers}
  226.   FItems := TaaHashTableLinear.Create(1021, AAELFHash);
  227.   FExpiryQueue := TaaPriorityQueueEx.Create(CompareExpiryDates);
  228.   FLastUsedQueue := TaaPriorityQueueEx.Create(CompareLastUsedDates);
  229. end;
  230. {--------}
  231. destructor TaaFileCache.Destroy;
  232. begin
  233.   if (FItems <> nil) then begin
  234.     fcSaveToFile;
  235.     Clear;
  236.     FItems.Free;
  237.     FExpiryQueue.Free;
  238.     FLastUsedQueue.Free;
  239.   end;
  240.   inherited Destroy;
  241. end;
  242. {--------}
  243. procedure TaaFileCache.Add(const aExternalName : string;
  244.                            const aExpiryDate   : TDateTime;
  245.                                  aStream       : TStream);
  246. var
  247.   CacheItem    : TCacheItem;
  248.   InternalName : string;
  249.   QualName     : string;
  250.   Stream       : TFileStream;
  251. begin
  252.   {create a unique file name}
  253.   InternalName := fcGetUniqueFileName;
  254.   QualName := fcGetQualifiedFileName(InternalName);
  255.   {create a new cache item}
  256.   CacheItem := nil;
  257.   try
  258.     CacheItem := TCacheItem.Create(InternalName, aExternalName,
  259.                                    aExpiryDate, Now, aStream.Size);
  260.     {try and add the item}
  261.     if not fcAddItem(CacheItem) then begin
  262.       {if it already exists, delete the unique file we created}
  263.       DeleteFile(QualName);
  264.     end
  265.     else
  266.       {otherwise copy the stream over}
  267.       Stream := TFileStream.Create(QualName, fmOpenReadWrite);
  268.       try
  269.         Stream.CopyFrom(aStream, 0);
  270.       finally
  271.         Stream.Free;
  272.       end;
  273.     end;
  274.     {check the maximum disk usage}
  275.     if (FCurDiskSize > MaxDiskSize) then
  276.       fcCleanUp;
  277.   except
  278.     {if a problem occurred, we need to delete the cache item and the
  279.      internal file, and reraise the exception}
  280.     CacheItem.Free;
  281.     DeleteFile(QualName);
  282.     raise;
  283.   end;
  284. end;
  285. {--------}
  286. procedure TaaFileCache.Clear;
  287. var
  288.   i : integer;
  289. begin
  290.   {clear the two queues}
  291.   while (FExpiryQueue.Count > 0) do
  292.     FExpiryQueue.Remove;
  293.   while (FLastUsedQueue.Count > 0) do
  294.     FLastUsedQueue.Remove;
  295.   {clear the hash table}
  296.   for i := 0 to pred(FItems.TableSize) do
  297.     TCacheItem(FItems[i]).Free;
  298.   FItems.Empty;
  299.   {there are no files, hence the disk usage is 0}
  300.   FCurDiskSize := 0;
  301. end;
  302. {--------}
  303. procedure TaaFileCache.Delete(const aExternalName : string);
  304. var
  305.   CacheItem : TCacheItem;
  306.   Handle    : TaaPQHandle;
  307. begin
  308.   {find the cache item for this external name}
  309.   if FItems.Find(aExternalName, pointer(CacheItem)) then begin
  310.     {if the cache item has a data stream, it's in use so we can't
  311.      delete it: raise an exception}
  312.     if (CacheItem.DataStream <> nil) then
  313.       raise Exception.Create('TaaFileCache.Delete: file is in use');
  314.     {delete the cache item from the two queues}
  315.     Handle := CacheItem.ExpiryHandle;
  316.     FExpiryQueue.Delete(Handle);
  317.     Handle := CacheItem.LastUsedHandle;
  318.     FLastUsedQueue.Delete(Handle);
  319.     {delete the item from the hash table}
  320.     FItems.Delete(aExternalName);
  321.     {reduce the total disk usage}
  322.     dec(FCurDiskSize, CacheItem.Size);
  323.     {free the cache item}
  324.     CacheItem.Free;
  325.   end;
  326. end;
  327. {--------}
  328. function TaaFileCache.fcAddItem(aCacheItem : pointer) : boolean;
  329. var
  330.   CacheItem : TCacheItem;
  331.   Dummy     : pointer;
  332. begin
  333.   {typecast the cache item to something recognizable}
  334.   CacheItem := TCacheItem(aCacheItem);
  335.   {make sure it isn't already in the cache, if it is free the passed
  336.    object to make sure we don't have a leak}
  337.   if FItems.Find(CacheItem.ExternalName, Dummy) then begin
  338.     CacheItem.Free;
  339.     Result := false;
  340.     Exit;
  341.   end;
  342.   Result := true;
  343.   {add it to the hash table first}
  344.   FItems.Insert(CacheItem.ExternalName, CacheItem);
  345.   {add it to the expiry queue}
  346.   CacheItem.ExpiryHandle := FExpiryQueue.Add(CacheItem);
  347.   {add it to the lastused queue}
  348.   CacheItem.LastUsedHandle := FLastUsedQueue.Add(CacheItem);
  349.   {increment the disk size}
  350.   inc(FCurDiskSize, CacheItem.Size);
  351. end;
  352. {--------}
  353. procedure TaaFileCache.fcCleanUp;
  354. var
  355.   CacheItem : TCacheItem;
  356.   StaticNow : TDateTime;
  357. begin
  358.   StaticNow := Now;
  359.   {first check our expiry dates}
  360.   CacheItem := FExpiryQueue.Peek;
  361.   while (FCurDiskSize > MaxDiskSize) and
  362.         (CacheItem <> nil) and
  363.         (CacheItem.ExpiryDate < StaticNow) do begin
  364.     Delete(CacheItem.ExternalName);
  365.     CacheItem := FExpiryQueue.Peek;
  366.   end;
  367.   {if we've reduced the disk usage enough, exit}
  368.   if (FCurDiskSize < MaxDiskSize) then
  369.     Exit;
  370.   {now start getting rid of old, not recently used stuff}
  371.   CacheItem := FLastUsedQueue.Peek;
  372.   while (FCurDiskSize > MaxDiskSize) do begin
  373.     Delete(CacheItem.ExternalName);
  374.     CacheItem := FLastUsedQueue.Peek;
  375.   end;
  376. end;
  377. {--------}
  378. function TaaFileCache.fcGetQualifiedFileName(const aFileName : string)
  379.                                                              : string;
  380. begin
  381.   if (Folder = '') then
  382.     raise Exception.Create('No folder defined for cache');
  383.   if (Folder[length(Folder)] <> '\') then
  384.     Result := ExpandFileName(Folder + '\' + aFileName)
  385.   else
  386.     Result := ExpandFileName(Folder + aFileName);
  387. end;
  388. {--------}
  389. function TaaFileCache.fcGetUniqueFileName : string;
  390. var
  391.   TempName : array [0..MAX_PATH] of char;
  392. begin
  393.   {get and create a file with a unique name in the folder}
  394.   if (GetTempFileName(PChar(FFolder), 'AAC', 0, TempName) = 0) then
  395.     RaiseLastWin32Error;
  396.   {return this file name}
  397.   Result := ExtractFileName(StrPas(TempName));
  398. end;
  399. {--------}
  400. procedure TaaFileCache.fcLoadFromFile;
  401. var
  402.   FileStream : TFileStream;
  403.   FileName   : string;
  404. begin
  405.   {get the name of the index file}
  406.   FileName := fcGetQualifiedFileName(CacheFileName);
  407.   {if it doesn't exist, we're starting afresh}
  408.   if not FileExists(FileName) then
  409.     Clear
  410.   {if it does exist, load it}
  411.   else begin
  412.     FileStream := TFileStream.Create(FileName, fmOpenRead);
  413.     try
  414.       fcLoadFromStream(FileStream);
  415.     finally
  416.       FileStream.Free;
  417.     end;
  418.   end;
  419. end;
  420. {--------}
  421. procedure TaaFileCache.fcLoadFromStream(aStream : TStream);
  422. var
  423.   ItemCount : integer;
  424.   i         : integer;
  425.   CacheItem : TCacheItem;
  426.   FileName  : string;
  427. begin
  428.   {clear out the current index}
  429.   Clear;
  430.   {now read the count of items in the stream}
  431.   aStream.ReadBuffer(ItemCount, sizeof(ItemCount));
  432.   {now read in and add all the items}
  433.   for i := 0 to pred(ItemCount) do begin
  434.     CacheItem := TCacheItem.LoadFromStream(aStream);
  435.     FileName := fcGetQualifiedFileName(CacheItem.InternalName);
  436.     if not FileExists(FileName) then
  437.       CacheItem.Free
  438.     else
  439.       fcAddItem(CacheItem);
  440.   end;
  441. end;
  442. {--------}
  443. procedure TaaFileCache.fcSaveToFile;
  444. var
  445.   FileStream : TFileStream;
  446.   FileName   : string;
  447. begin
  448.   {get the name of the index file}
  449.   FileName := fcGetQualifiedFileName(CacheFileName);
  450.   {save the index to this file}
  451.   FileStream := TFileStream.Create(FileName, fmCreate);
  452.   try
  453.     fcSaveToStream(FileStream);
  454.   finally
  455.     FileStream.Free;
  456.   end;
  457. end;
  458. {--------}
  459. procedure TaaFileCache.fcSaveToStream(aStream : TStream);
  460. var
  461.   ItemCount : integer;
  462.   i         : integer;
  463.   CacheItem : TCacheItem;
  464. begin
  465.   {first write out a count of items we have (we'll use the lastused
  466.    list for the count and for the items: it's easier)}
  467.   ItemCount := FLastUsedQueue.Count;
  468.   aStream.WriteBuffer(ItemCount, sizeof(ItemCount));
  469.   {now write out all the items}
  470.   for i := 0 to pred(FItems.TableSize) do begin
  471.     CacheItem := FItems[i];
  472.     if (CacheItem <> nil) then
  473.       CacheItem.SaveToStream(aStream);
  474.   end;
  475. end;
  476. {--------}
  477. procedure TaaFileCache.fcSetFolder(const aName : string);
  478. begin
  479.   if (CompareText(aName, FFolder) <> 0) then begin
  480.     {store the index for the current folder}
  481.     if (Folder <> '') then
  482.       fcSaveToFile;
  483.     {switch folders}
  484.     FFolder := aName;
  485.     {try and read the index for the new folder}
  486.     if (Folder <> '') then
  487.       fcLoadFromFile;
  488.   end;
  489. end;
  490. {--------}
  491. function TaaFileCache.Get(const aExternalName : string) : TStream;
  492. var
  493.   CacheItem : TCacheItem;
  494.   FileStrm  : TFileStream;
  495.   FileName  : string;
  496. begin
  497.   {try and find the cache item in the hash table, if not there, return
  498.    a nil stream}
  499.   if not FItems.Find(aExternalName, pointer(CacheItem)) then begin
  500.     Result := nil;
  501.     Exit;
  502.   end;
  503.   {the item was found, so create a buffered file stream for it}
  504.   FileName := fcGetQualifiedFileName(CacheItem.InternalName);
  505.   FileStrm := TFileStream.Create(FileName, fmOpenRead+fmShareDenyWrite);
  506.   CacheItem.DataStream := TaaBufferedStream.Create(FileStrm, 4096);
  507.   Result := CacheItem.DataStream;
  508. end;
  509. {--------}
  510. procedure TaaFileCache.GetComplete(const aExternalName : string);
  511. var
  512.   CacheItem : TCacheItem;
  513. begin
  514.   {find the cache item for this external name}
  515.   if FItems.Find(aExternalName, pointer(CacheItem)) then begin
  516.     {if the cache item has a data stream...}
  517.     if (CacheItem.DataStream <> nil) then begin
  518.       {free the stream(s)}
  519.       if (CacheItem.DataStream is TaaBufferedStream) then
  520.         TaaBufferedStream(CacheItem.DataStream).Stream.Free;
  521.       CacheItem.DataStream.Free;
  522.       CacheItem.DataStream := nil;
  523.     end;
  524.     {we've just used the cache item, so update its last-used date }
  525.     CacheItem.Use;
  526.     {reprioritize the lsat used date queue for this item}
  527.     FLastUsedQueue.Replace(CacheItem.LastUsedHandle, CacheItem);
  528.   end;
  529. end;
  530. {====================================================================}
  531.  
  532. end.
  533.